package com.learn.demo.proxy;

import com.learn.demo.entity.Ctest;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 接口实例化动态代理
 *
 * @author banjiawei
 * @date 2021/03/30
 */
@Slf4j
public class ProxyInterfaceDemo {
    public static void main(String[] args) {
        CtestMapper ctestMapper = (CtestMapper) Proxy.newProxyInstance(ProxyInterfaceDemo.class.getClassLoader(), new Class[]{CtestMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Map<String, Object> methodArgsMap = buildMethodArgsMap(method, args);
                for (Object arg : args) {
                    log.info("arg:{}", arg.toString());
                }
                Select annotation = method.getAnnotation(Select.class);
                if(null != annotation){
                    String[] values = annotation.value();
                    for (String value : values) {
                        log.info("sql before parse:{}", value);
                        String sql = parseSQL(value, methodArgsMap);
                        log.info("sql after parse:{}", sql);
                        log.info("method.getReturnType():{}",method.getReturnType());
                        log.info("method.getGenericReturnType():{}",method.getGenericReturnType());
                    }
                }
                return null;
            }
        });
        ctestMapper.selectCtestList(9, "测试");
    }

    /**
     * 解析并替换SQL中的表达式
     * @param sql
     * @param methodArgsMap
     * @return
     * @throws Exception
     */
    public static String parseSQL(String sql, Map<String, Object> methodArgsMap) throws Exception {
        StringBuilder parseSQL = new StringBuilder();
        int length = sql.length();
        for (int i = 0; i < length; i++) {
            if(sql.charAt(i) == '#'){
                int netIndex = i + 1;
                char nextChar = sql.charAt(netIndex);
                if(nextChar != '{'){
                    throw new RuntimeException(String.format("#号后面需要跟{号，错误SQL为：%s", sql));
                }
                StringBuilder argSB = new StringBuilder();
                i = parseSQLArg(argSB, sql, netIndex);
                String argName = argSB.toString();
                Object argValue = methodArgsMap.get(argName);
                if(null == argValue){
                    throw new RuntimeException(String.format("找不到参数param:[%s]对应的参数值paramValue:[%s]\nindex:%s\nsql:%s", argName, argValue, i, sql));
                }
                parseSQL.append(argValue.toString());
                continue;
            }
            parseSQL.append(sql.charAt(i));
        }
        return parseSQL.toString();
    }

    /**
     * 替换SQL中表达式的值
     * @param argSB
     * @param sql
     * @param netIndex
     * @return
     * @throws Exception
     */
    private static int parseSQLArg(StringBuilder argSB, String sql, int netIndex) throws Exception {
        for (netIndex++; netIndex < sql.length() ; netIndex++) {
            char sqlChar = sql.charAt(netIndex);
            if(sqlChar != '}'){
                argSB.append(sql.charAt(netIndex));
            }
            if(sqlChar == '}'){
                return netIndex;
            }
        }
        throw new RuntimeException(String.format("左括号{后面应该跟右括号},该错误SQL为:%s", sql));
    }

    /**
     * 构建参数名称和参数值对应关系
     * @param method
     * @param args
     * @return
     * @throws Exception
     */
    public static Map<String, Object> buildMethodArgsMap(Method method, Object[] args) throws Exception {
        Map<String, Object> result = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        if(parameters == null || parameters.length == 0){
            throw new RuntimeException("参数为空");
        }
        for (int i = 0; i < parameters.length; i++) {
            result.put(parameters[i].getName(), args[i]);
        }
        return result;
    }
}

interface CtestMapper {
    @Select("SELECT id, k, remark, create_time FROM ctest WHERE id = #{id} and remark = #{remark}")
    List<Ctest> selectCtestList(Integer id, String remark);
}